Cross-Site Scripting

Cross-site scripting (XSS) describes a set of attacks where an adversary injects Javascript into a web application, typically because user input isn't properly sanitised. It is similar to HTML injection, however, it allows for the execution of Javascript code and that makes it a potentially critical vulnerability.

All XSS vulnerabilities can be categorised either as stored XSS or reflected XSS.

Reflected XSS

Reflected XSS is the simplest type of XSS and arises when a web application includes data from an HTTP request into the HTTP response unsafely. Suppose a web application has a search function and obtains the search criteria from an HTTP request through a URL parameter:

https://example.com/search?item=name

It is not difficult to imagine that the application might include the item's name in the resulting web page, for example in a paragraph like the following:

<p>Results for: name</p>

If the value of the item parameter is not sanitised before being embedded into the resulting web page, an adversary can craft a malicious link like https://example.com/search?item=<script>evil code</script> and send it to a victim user. If they click on the link, the script inside it will be executed by their browser and they will be compromised.

Example: Reflected XSS

Once again, there is a great PortSwigger lab which demonstrates reflected XSS. All we need to do, is enter our payload in the search field and click Search.

The resulting web page contains the malicious script in its URL. If we send it to an unsuspecting victim and they open it, their browser will execute the script and the user will also see the alert pop-up.

Info: Self-XSS

Self-XSS is a subtype of reflected XSS which cannot be triggered via a crafted URL or a cross-domain request. Instead, this vulnerability requires that the victim themselves submits the XSS payload from their browser and usually and usually necessitates social engineering. As such, self-XSS vulnerabilities are considered low-impact.

Stored XSS

Stored XSS (also known as persistent or second-order XSS) occurs when the exploit payload is stored on the server, typically in the database. When a legitimate user later views a vulnerable page which incorporates the stored data, the exploit will be injected into it and it will be executed by the user's browser.

Example: Stored XSS

This PortSwigger lab is an excellent illustration of stored XSS. We notice that we can leave comments under the posts, so we should check for XSS.

Once we have posted our malicious comment, navigating to the post, where the comment is displayed, results in the triggering of the alert prompt.

Injection Points

When exploring an XSS vulnerability, it is crucial to identify the injection point (i.e. the context) where the payload is injected.

XSS between HTML Tags

The most common injection point for XSS is between existing HTML tags on the page. Executing code in this context necessitates the introduction of new HTML tags such as script or other elements with events. Here are some example payloads:

<script>alert(1);</script>
<img src=1 onerror=alert(1)>

XSS between HTML Attributes

Another possible injection point for XSS is within attributes located in an existing HTML tag. To exploit such vulnerabilities, one needs to terminate the attribute they are injecting in by inserting a double quotes character ("). For example:

"><script>alert(1);</script>

However, brackets are usually filtered or encoded in such contexts and so one cannot terminate the actual tag and insert new ones. Nevertheless, if one can terminate the attribute value with ", they can usually insert additional attributes into the tag, which can still lead to XSS, typically through events:

" src=1 onerror=alert(1) 

Any attributes which expect a URI can themselves provide a scriptable context which means that JavaScript can be executed without terminating the attribute. This is done by dint of the javascript pseudo protocol:

javascript: alert(1)

A list of these attributes and the tags that can contain them can be found here.

XSS in JavaScript

Sometimes, the injection point is within an already existing pair of script tags. This usually happens within string literals such as in the following case.

var input = 'user input';

Therefore, before injecting code, one needs to first terminate the string literal. Moreover, one must also repair the script lest syntax errors preclude the execution of the entire code block. This can be achieved via on of the two following ways:

'-alert(1);//'
';alert(1);//'

Some applications try to prevent this by escaping any single-quote characters with a backslash which tells the interpreter to treat the character literally rather than as a special character (in this case a string literal terminator). However, these often forget to escape the backslash character itself. This means that the backslash inserted by the application can be nullified by placing a backslash in the payload:

\'alert(1);//

The application will convert this to \\'alert(1);// and the two backslashes will neutralise each other.

Prevention

Encoding Data on Output

This is the first line of defence against XSS attacks. User input should be correctly encoded directly before it is written to the output page. The reason for this is that different contexts require different encoding strategies.

In an HTML context, non-whitelisted values should be converted into HTML entities:

  • < -> &lt;
  • > -> &gt;

By comparison, a JavaScript context requires URL encoding:

  • < -> \u003c
  • > -> \u003e

Sometimes, such as in HTML attributes, multiple layers of encoding might be necessary and they should also be applied in the correct order. For example, safely embedding user input into an event handler attribute first necessitates HTML encoding and then JavaScript encoding.

In PHP

PHP has a nice function called htmlentities which can be used when escaping user input within an HTML context. It takes three arguments:

  • The input string.
  • ENT_QUOTES - a flag signifying that all quotes should be encoded.
  • The character set - most commonly UTF-8.

Here is a sample invocation:

`<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>`

Unfortunately, PHP does not provide an API for Unicode-encoding a string, so escaping user input in a JavaScript context has to be done manually.

In JavaScript

JavaScript does not provide APIs for either HTML or Unicode encoding. Therefore, these must be manually implemented. The same holds for the jQuery framework.

Content Security Policy (CSP)

Content Security Policy (CSP) is a mechanism used to mitigate XSS. It works by restricting the source of the various resources (such as scripts and images) that a page uses. For CSP to be enabled, the HTTP response of the server has to include a Content-Security-Policy header followed by the actual CSP, which is a semicolon-separated list of one or more directives.

The following directive will only allow scripts to be loaded if they originate from the same source as the page itself:

script-src 'self'

One can also whitelist specific external domains as allowed sources:

script-src https://scripts.example.com

These two directive have equivalents for the sources of images:

image-src 'self'
image-src https://images.example.com

One should proceed cautiously when whitelisting domains because if an adversary can obtain a way to upload content to that domain, then they can bypass CSP.